import { readFileSync } from "node:fs";
import { writeFile } from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { dedent } from "../src/utils/dedent";
import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test";

const seed = {
	"wrangler.toml": dedent`
		name = "test-worker"
		main = "src/index.ts"
		compatibility_date = "2023-01-01"
		compatibility_flags = ["nodejs_compat", "no_global_navigator"]
		[vars]
		MY_VAR = "my-var-value"
	`,
	"src/index.ts": dedent`
		export default {
			fetch(request) {
				return new Response("Hello World!")
			}
		}
	`,
	"package.json": dedent`
		{
			"name": "test-worker",
			"version": "0.0.0",
			"private": true
		}
	`,
};

describe("types", () => {
	it("should generate runtime types without a flag", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed(seed);
		const output = await helper.run(`wrangler types`);

		expect(output.stdout).toContain("Generating runtime types...");
		expect(output.stdout).toContain("Runtime types generated.");
		expect(output.stdout).toContain(
			"✨ Types written to worker-configuration.d.ts"
		);
		expect(output.stdout).toContain("📖 Read about runtime types");
	});

	it("should generate runtime types and env types in one file at the default path", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed(seed);
		const output = await helper.run(`wrangler types`);
		expect(output.stdout).toContain("Generating project types...");
		expect(output.stdout).toContain("interface Env {");
		expect(output.stdout).toContain("Generating runtime types...");
		expect(output.stdout).toContain("Runtime types generated.");
		expect(output.stdout).toContain(
			"✨ Types written to worker-configuration.d.ts"
		);
		const file = readFileSync(
			path.join(helper.tmpPath, "./worker-configuration.d.ts"),
			"utf8"
		);
		expect(file).matches(/declare module ['"]cloudflare:workers["']/);
		expect(file).contains("interface Env");
	});

	it("should be able to generate an Env type only", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed(seed);
		const output = await helper.run(`wrangler types --include-runtime=false`);
		expect(output.stdout).not.toContain("Generating runtime types...");
		const file = readFileSync(
			path.join(helper.tmpPath, "./worker-configuration.d.ts"),
			"utf8"
		);
		expect(file).toMatchInlineSnapshot(`
			"/* eslint-disable */
			// Generated by Wrangler by running \`wrangler types --include-runtime=false\` (hash: 5c82572f95137bbfb775d02fdf441070)
			declare namespace Cloudflare {
				interface GlobalProps {
					mainModule: typeof import("./src/index");
				}
				interface Env {
					MY_VAR: "my-var-value";
				}
			}
			interface Env extends Cloudflare.Env {}
			"
		`);
	});

	it("should include header with version information in the generated types", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed(seed);
		await helper.run(`wrangler types "./types.d.ts" `);

		const lines = readFileSync(
			path.join(helper.tmpPath, "./types.d.ts"),
			"utf8"
		).split("\n");

		expect(lines[1]).toMatchInlineSnapshot(
			`"// Generated by Wrangler by running \`wrangler types ./types.d.ts\` (hash: 5c82572f95137bbfb775d02fdf441070)"`
		);
		expect(lines[2]).match(
			/\/\/ Runtime types generated with workerd@1\.\d{8}\.\d \d{4}-\d{2}-\d{2} ([a-z_]+,?)*/
		);
	});

	it("should include header with wrangler command that generated it", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed({
			...seed,
			"wranglerA.toml": dedent`
			name = "test-worker"
			main = "src/index.ts"
			compatibility_date = "2023-01-01"
		`,
		});
		await helper.run(
			"wrangler types -c wranglerA.toml --env-interface MyCloudflareEnv ./cflare-env.d.ts"
		);

		const lines = readFileSync(
			path.join(helper.tmpPath, "./cflare-env.d.ts"),
			"utf8"
		).split("\n");

		expect(lines[1]).toMatchInlineSnapshot(
			`"// Generated by Wrangler by running \`wrangler types -c wranglerA.toml --env-interface MyCloudflareEnv ./cflare-env.d.ts\` (hash: e981fccb455c04c58ced00f3442b83ac)"`
		);
		expect(lines[2]).match(
			/\/\/ Runtime types generated with workerd@1\.\d{8}\.\d \d{4}-\d{2}-\d{2} ([a-z_]+,?)*/
		);
	});

	it("should not regenerate runtime types if the header matches, but should regenerate env types", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed(seed);
		await helper.run(`wrangler types`);

		const typesPath = path.join(helper.tmpPath, "worker-configuration.d.ts");
		const file = readFileSync(typesPath, "utf8").split("\n");

		await writeFile(
			typesPath,
			[
				file[0],
				file[1],
				file[2],
				"FAKE ENV",
				"// Begin runtime types",
				"FAKE RUNTIME",
			].join("\n")
		);

		await helper.run(`wrangler types`);

		const file2 = readFileSync(typesPath, "utf8");

		// regenerates env types
		expect(file2).toContain("interface Env {");
		// uses cached runtime types
		expect(file2).toContain("// Begin runtime types");
		expect(file2).toContain("FAKE RUNTIME");
	});

	it("should read .env files for secret env vars", async () => {
		const helper = new WranglerE2ETestHelper();
		await helper.seed(seed);
		await helper.seed({
			".env": dedent`
				MY_SECRET_VAR=secret-value
			`,
		});
		const output = await helper.run(`wrangler types --include-runtime=false`);
		expect(output.stdout).not.toContain("Generating runtime types...");
		const file = readFileSync(
			path.join(helper.tmpPath, "./worker-configuration.d.ts"),
			"utf8"
		);
		expect(file).toMatchInlineSnapshot(`
			"/* eslint-disable */
			// Generated by Wrangler by running \`wrangler types --include-runtime=false\` (hash: 1457dc49fa39d3fb92583c9c66b2dbe5)
			declare namespace Cloudflare {
				interface GlobalProps {
					mainModule: typeof import("./src/index");
				}
				interface Env {
					MY_VAR: "my-var-value";
					MY_SECRET_VAR: string;
				}
			}
			interface Env extends Cloudflare.Env {}
			"
		`);
	});
});
